![]() |
![]() |
|
Das ist schon alles! Unser Attribut soll nur wie eine boolesche Variable operieren, die gesetzt ist oder nicht. Daher benötigen wir in diesem Fall auch keinen weiteren Programmcode. Zwei Fakten sind ausschlaggebend, um aus einer Klassendefinition ein Attribut zu machen:
Die Voranstellung eines Attributs mit einer Parameterliste vor der eigentlichen Attributdefinition hat den Grund, hier bereits elementare Eigenschaften der neuen Attributklasse festzulegen. In diesem Zusammenhang sind drei Parameter besonders interessant:
Während AttributeTargets angegeben werden muss, sind die beiden anderen optional. AttributeTargetsJedes Attribut kann sich nur auf bestimmte Codeelemente auswirken. Diese werden mit AttributeTargets bekannt gegeben. Im Beispiel oben wird mit AttributeTargets.All zum Ausdruck gebracht, dass wir das benutzerdefinierte Attribut auf alle Elemente anwenden können, aber das muss nicht immer so sein. Man kann den Einsatz eines Attributs ebenso gut nur auf Methoden oder Felder beschränken. Es steht dabei immer die Frage im Vordergrund: Was soll das Attribut letztendlich bewirken, welche Elemente sollen über das Attribut beeinflusst werden? AttributeTargets ist in der .NET-Klassenbibliothek als Enumeration vordefiniert und enthält die in der folgenden Tabelle aufgeführten Entitäten angewandt werden.
Natürlich steht auch hinter AttributeUsageAttribute eine Klassendefinition, die im Namespace System zu finden ist. Im Grunde genommen unterscheidet sich eine Klasse, die ein Attribut beschreibt, nicht von einer herkömmlichen Klasse. Daher verwundert es nicht, dass die Klasse AttributeUsageAttribute einen Konstruktor definiert, der einen Parameter vom Typ AttributeTargets erwartet:
Der Parameter validon vom Typ AttributeTargets wird bitweise interpretiert. Jedes Bit beschreibt dabei ein Element, auf welches das Attribut angewendet werden kann. Möchte man das Attribut mehreren verschiedenen Elementen zugänglich machen, muss man mehrere AttributeTargets-Konstanten bitweise verknüpfen, z. B.:
In diesem Fall wird ein Attribut bereitgestellt, das sowohl Methoden als auch Eigenschaften als Informationsquelle dienen kann. InheritedEine Klasse kann ihre Mitglieder einer abgeleiteten Klasse vererben. Einem Entwickler stellt sich natürlich die Frage, ob das Attribut in den Vererbungsprozess mit einbezogen wird oder ob es Gründe gibt, es davon auszuschließen. Einem benutzerdefiniertem Attribut teilen wir dies durch den booleschen Parameter Inherited mit, den wir optional AttributeUsageAttribute übergeben können. Standardmäßig ist der Wert auf True festgelegt, demnach vererbt sich ein gesetztes Attribut in einer Vererbungshierarchie weiter. AllowMultipleIn wohl eher seltenen Fällen kann es erforderlich sein, ein Attribut demselben Element mehrfach zuzuweisen. Diese Situation wäre denkbar, wenn man über das Attribut einem Element mehrere Feldinformationen zukommen lassen möchte. Dann muss man die mehrfache Anwendung eines Attributs explizit gestatten. Zur Lösung geben Sie den Parameter
an. Verzichten Sie auf diese Angabe, kann ein Attribut per Definition mit einem bestimmten Element nur einmal verknüpft werden. Konstruktoren eines AttributsWeiter oben haben Sie bereits erfahren, dass es möglich ist, in einer Attributklasse einen Konstruktor zu definieren. Im folgenden Beispiel ist das Attribut DeveloperAttribute definiert, das zwei Daten-Member enthält, von denen einer durch den Konstruktoraufruf initialisiert wird. Durch AttributeTargets geben wir bekannt, dass das Attribut jedem beliebigen Codeelement angeheftet werden kann.
Der Konstruktor nimmt einen Parameter entgegen, nämlich den Wert für das Feld Zuname. Bevor Sie sich darüber Gedanken machen, wie man das Feld PersID initialisiert, sehen Sie sich an, wie das Attribut auf eine Klasse angewendet wird:
Mit dieser Definition wird der Konstruktor unter Übergabe einer Zeichenfolge aufgerufen. Das zweite Feld des Attributs (PersID) wird mit keinem bestimmten Wert initialisiert, es enthält 0. Selbstverständlich könnten wir innerhalb der Attributdefinition für PersID einen von 0 abweichenden Wert festlegen, aber dieser ist dann natürlich für jedes verknüpfte Element identisch. Positionale und benannte ParameterUm PersID einen individuellen Wert zuzuweisen, lässt sich DeveloperAttribute auch wie folgt an die Klasse heften:
Beachten Sie, dass wir jetzt zwei Argumente übergeben, obwohl der Konstruktor nur einen Parameter vorsieht. Dies ist ein besonderes Merkmal von Attributen, denn beim Initialisieren eines Attributs können Sie sowohl positionale als auch benannte Parameter verwenden.
In unserem Beispiel ist Zuname ein positionaler Parameter, dem die Zeichenfolge »Meier« übergeben wird, während PersID ein benannter Parameter ist. Beachten Sie die etwas ungewöhnliche Notation bei der Übergabe des Wertes: Es wird ein Doppelpunkt, gefolgt vom Zuweisungsoperator, gesetzt. Benannte Parameter sind sehr flexibel. Einerseits können sie Standardwerte aufweisen, die grundsätzlich immer gültig sind, andererseits kann der Wert im Bedarfsfall individuell festgelegt werden. Ob Sie einen Daten-Member positional oder benannt einsetzen wollen, ist die erste Entscheidung, die sich an den spezifischen Anforderungen orientiert. Ob ein benannter Parameter neu festgelegt werden muss oder der Standardwert akzeptabel ist, hängt vom Einzelfall ab. Die Möglichkeit, benannte Parameter vorzusehen, befreit Sie von der Verpflichtung, für jede denkbare Kombination von Feldern und Eigenschaften überladene Konstruktoren in der Attributdefinition vorsehen zu müssen. Andererseits wird Ihnen damit aber nicht die Alternative entzogen, dennoch den Konstruktor zu überladen. Da unterscheiden sich die herkömmlichen Klassendefinitionen nicht von denen der Attribute.
Die Reihenfolge der benannten Parameter ist beliebig, da der Compiler aufgrund der Parameternamen richtig zuordnen kann. Benannte Parameter können alle öffentlich deklarierten Felder oder Eigenschaften sein – vorausgesetzt, sie sind weder statisch noch konstant definiert. Die Daten eines Attributs auswertenEin Attribut soll einen steuernden Einfluss auf das Laufzeitverhalten eines Elements ausüben oder es mit wesentlichen Informationen versorgen. Enthält das Attribut Daten, ist es für das mit einem Attribut markierte Element wichtig, an die Daten des Attributs zu gelangen. Im folgenden Codefragment soll dies für das Beispiel unseres eben entwickelten Developer-Attributes gezeigt werden. Dazu schreiben wir den folgenden Code:
An dieser Stelle sei zunächst darauf hingewiesen, dass wir in der Implementierung von Main davon ausgehen, dass nur unser benutzerdefiniertes Attribut DeveloperAttribute gesetzt ist – ein Element kann natürlich auch mit mehreren unterschiedlichen Attributen verknüpft sein. Im Code müssten wir dann sondieren und die Klasse auf alle gesetzten Attribute abfragen. Um die Komplexität des Codes in Grenzen zu halten, wird auf diese Untersuchung verzichtet. Um die Daten eines Attributs zu erhalten, benötigen wir eine Referenz vom Typ des gesuchten Attributs. Dazu deklarieren wir eine Objektvariable und weisen ihr den gewünschten Typ des Attributs zu. Die Klasse System.Attribute stellt dazu die überladene, statische Methode GetCustomAttribute zur Verfügung. In unserem Fall eignet sich die folgende Variante:
Dem ersten Argument teilen wir den Typ des attributführenden Objekts mit, im zweiten Parameter geben wir den Attributtyp an. Damit stehen wir vor der nächsten Schwierigkeit, nämlich der Bestimmung des Typs beider Argumente. Die Sprache Visual Basic 2005 stellt zu diesem Zweck den GetType-Operator bereit, dem als Operand der Typname übergeben wird und der als Ergebnis der Operation das Type-Objekt des spezifizierten Typs liefert.
Die Klasse Type ist eine der fundamentalsten Klassen des .NET Frameworks. Möchte man per Programmcode zur Laufzeit auf die typbeschreibenden Metadaten einer Komponente zugreifen, nimmt diese abstrakte Klasse eine wesentliche Schlüsselrolle ein, denn über ein Type-Objekt erhält man alle Informationen über einen bestimmten Typ. So könnte man beispielsweise eine Liste aller Felder, Schnittstellen, Properties, Methoden, Konstruktoren usw. abfragen, die in diesem Typ definiert sind. Type analysiert also in allen Einzelheiten einen .NET-Typ und liefert jede benötigte Information. Der Prozess, der den Zugriff auf die Metadaten ermöglicht, wird als Reflexion bezeichnet. Kommen wir zurück zu unserem Beispiel. Mit
erhalten wir die Referenz auf das Attribut vom Typ DeveloperAttribute, das die Klasse ClassA ziert. Da wir nun die Referenz auf das Attribut in den Händen halten, könnten wir sofort auf dessen öffentliche Member zugreifen. Um ganz sicherzugehen, dass das Attribut auch tatsächlich mit der Klasse verschmolzen ist, starten wir noch eine Abfrage (auf die wir allerdings in diesem Beispiel auch verzichten könnten), ob die Variable attr eine gültige Referenz enthält. Würde das Attribut nämlich nicht gefunden, wäre attr gleich Nothing.
An der Konsole wird nun erwartungsgemäß
ausgegeben und erfüllt uns mit einer gewissen Befriedigung. Wenn Sie das Beispiel in der Entwicklungsumgebung nachvollziehen, sollten Sie die Attributverknüpfung entfernen und die Anwendung nochmals starten. Das Attribut wird nicht gefunden und an der Konsole
ausgegeben.
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken.
Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die
gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich
geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung,
Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.